#include "ParticleSystem.h"
#include "glm/gtx/rotate_vector.hpp"
#include "Graphics/Effect.h"
#include "Graphics/RenderTarget.h"
#include "Utils/Curve.h"

using namespace RayFrame;

ParticleEmitter::ParticleEmitter(int particleNumber)
{
  create(particleNumber);
}

void ParticleEmitter::create(int particleNumber)
{
  m_parameter.Color = glm::vec4(1.5, 0.7, 1.0, 1.0);
  m_parameter.Count = particleNumber;
  m_parameter.ParticleLifeTime = 4.f;
  m_parameter.ParticleLifeTimeVar = 0.1f;
  m_parameter.TextureRef = Texture::CreateFromFile("fireball.dds");
  //Texture::CreateFromFile("sprites/black_smoke.dds");
  m_parameter.TilesX = 1;
  m_parameter.TilesY = 1;
  m_parameter.Alpha = 0.0f;
  m_parameter.Size = 2.5f;
  m_parameter.SizeRandom = 0.3f;
  m_parameter.Speed = glm::vec3(0.0f, 0.2f, 0.0f);
  m_parameter.SpeedRandom = 0.2f;
  m_parameter.EmitterPos = glm::vec3(0,0,0);
  m_parameter.RandomOffset = 0.1f * glm::vec3(1,1,1);

  m_parameter.InitAngles = glm::vec3(0,0,0);
  m_parameter.RandomAngles = glm::vec3(0, 0, 180);
  m_parameter.RandomRotationRate = 0.4f;
  m_parameter.RotationRate = glm::vec3(0, 0, 90);

  //float keyX[] = {0.f, 0.4f, 0.6f, 1.0f};
  //float keyY[] = {0.f, 1.0f, 0.7f, 0.f};
  //m_parameter.AlphaCurv.SetKeys(keyX, keyY, 4, Curve::LINEAR);
  float keyX[] = {0.f, 0.4f, 1.0f };
  float keyY[] = {0.f, 1.0f, 0.f};
  m_parameter.AlphaCurv.SetKeys(keyX, keyY, 3, Curve::LINEAR);

  // angles
  m_parameter.EmitAngle = 40.f;
  m_parameter.EmitAngleVar = 0.0f;

  m_particles.Init(int(m_parameter.Count * 1.5f));

  m_emitTimeAccumulate = 0;
}

ParticleEmitter::~ParticleEmitter(void)
{
}

void ParticleEmitter::Update(double elapsedTime)
{
  getBillboardMatrix(m_world, GetPosition());

  // update particles
  spawnParticle(elapsedTime);
  updateParticle(elapsedTime);
}

void ParticleEmitter::getBillboardMatrix(glm::mat4& mat, glm::vec3 pos)
{
  // calclate transform matrix
  glm::vec3 localZ = glm::normalize(g_App->GetCamera()->GetEye() - pos);
  glm::vec3 up(0,1,0);
  glm::vec3 localX = glm::normalize(glm::cross(up, localZ));
  glm::vec3 localY = glm::normalize(glm::cross(localZ, localX));
  glm::mat4 transform;
  transform[0] = glm::vec4(localX, 0);
  transform[1] = glm::vec4(localY, 0);
  transform[2] = glm::vec4(localZ, 0);
  //transform[0][0] = localX.x;
  //transform[1][0] = localX.y;
  //transform[2][0] = localX.z;
  //transform[0][1] = localY.x;
  //transform[1][1] = localY.y;
  //transform[2][1] = localY.z;
  //transform[0][2] = localZ.x;
  //transform[1][2] = localZ.y;
  //transform[2][2] = localZ.z;
  mat = glm::gtc::matrix_transform::translate(transform, -GetPosition());
}

void ParticleEmitter::updateParticle(double elapsedTime)
{
  std::vector<Pool<Particle>::iterator> deadParticles;

  for( Pool<Particle>::iterator iter = m_particles.UsedBegin();
    iter != m_particles.End(); iter++)
  {
    // check if particle is dead. if it is, move it to free list
    iter->LifeUsed += (float)elapsedTime;
    if(iter->LifeUsed >= iter->Life)
    {
      deadParticles.push_back(iter);
      continue;
    }

    // update particle state
    iter->Update((float)elapsedTime);
  }

  // recycle dead particles
  for(std::vector<Pool<Particle>::iterator>::iterator iter = deadParticles.begin();
    iter != deadParticles.end(); iter++)
  {
    m_particles.Push(*iter);
  }
}

void ParticleEmitter::BeforeRender()
{
}

void ParticleEmitter::Render(double elapsetTime)
{
  glBegin(GL_TRIANGLES);
  for(Pool<Particle>::iterator iter = m_particles.UsedBegin();
    iter != m_particles.End(); iter++)
  {
    float x = iter->Poistion.x;
    float y = iter->Poistion.y;
    float z = iter->Poistion.z;

    glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // Top Right
    glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Top Left
    glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Bottom Right

    glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Bottom Right
    glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Top Left
    glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // Bottom Left
  }
  glEnd();
}

void ParticleEmitter::AfterRender()
{
}

Particle* ParticleEmitter::spawnParticle(double elapsedTime)
{
  if(m_parameter.ParticleLifeTime < 0)
    m_parameter.ParticleLifeTime = 1.0;

  float emitDelta = m_parameter.ParticleLifeTime / m_parameter.Count;
  if( m_emitTimeAccumulate < emitDelta)
  {
    m_emitTimeAccumulate += elapsedTime;
    return NULL;
  }
  m_emitTimeAccumulate = 0.f;

  if(m_particles.Exahusted())
  {
    m_emitTimeAccumulate = emitDelta;
    return NULL;
  }

  Particle* p = &*m_particles.Pop();
  p->Reset(&m_parameter);
  return p;
}

void Particle::Reset(ParticleParameter* p)
{
  // Init this particle
  Poistion = p->EmitterPos + p->PosOffset +
    NormalizedRand() * p->RandomOffset;

  Alpha = p->Alpha;
  Color = p->Color;
  Size = p->Size;
  Life = p->ParticleLifeTime;//p->ParticleLifeTimeVar * NormalizedRand();
  LifeUsed = 0.f;
  Rotation = p->InitAngles + p->RandomAngles * NormalizedRand();
  RotationSpeed = p->RotationRate * ( NormalizedRand() * p->RandomRotationRate );
  Param = p;

  // direction
  Direction = glm::vec3(0,1,0);
  Direction = glm::rotateX(Direction, p->EmitAngle * NormalizedRand() );
  Direction = glm::rotateY(Direction, 360.f * NormalizedRand() );
}

void Particle::Update(float timeElapsed)
{
  Poistion += Direction * timeElapsed;
  Rotation += RotationSpeed * timeElapsed;

  Alpha = Param->AlphaCurv.GetValue(LifeUsed / Life, 1);
}

void RayFrame::ParticleEmitter::Render( double elapsedTime, Effect* effect,
                                       RenderTarget* rt, Texture* texDepth)
{
  //g_renderer->Clear(SOFT_BLUE);
  glDepthMask(GL_FALSE);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  g_renderer->EnableTexture();

  if(rt)
    rt->Begin();

  effect->SelectPass("pass_particle");
  effect->SetShaderParam("TexSmoke", m_parameter.TextureRef);
  effect->SetShaderParam("TilesU", m_parameter.TilesX);
  effect->SetShaderParam("TilesV", m_parameter.TilesY);
  effect->SetShaderParam("DiffuseColor", m_parameter.Color);

  int useAlpha = Configure::InstanceRef().GetValueNum<int>(PARTICLE_USE_ALPHA);
  effect->SetShaderParam("UseAlpha", useAlpha);

  if(texDepth)
    effect->SetShaderParam("TexDepth", texDepth);

  //const int tilesCount = m_parameter.TilesX * m_parameter.TilesY;
  const float tilesCount = 64;

  for(Pool<Particle>::iterator iter = m_particles.UsedBegin();
    iter != m_particles.End(); iter++)
  {
    glm::mat4x4 scale, rotate, translate, billboard;
    getBillboardMatrix(billboard, GetPosition() + iter->Poistion);
    scale = glm::gtc::matrix_transform::scale(scale,
      glm::vec3(1,1,1) * iter->Size);
    rotate = glm::gtc::matrix_transform::rotate( rotate,
      iter->Rotation.z, glm::vec3(0,0,1));
    translate = glm::gtc::matrix_transform::translate( translate,
      iter->Poistion);
    effect->SetShaderParam("Projection", 
      g_App->GetCamera()->GetMatrixProjection());
    effect->SetShaderParam("ModelView",
                           g_App->GetCamera()->GetMatrixModelView() *
                           translate * scale * billboard * rotate);

    //effect->SetShaderParam("FrameID",debug);
//     effect->SetShaderParam("FrameID", floorf(iter->LifeUsed/iter->Life *
//       tilesCount));
    effect->SetShaderParam("Life", iter->LifeUsed / iter->Life);
    effect->SetShaderParam("Alpha", iter->Alpha);
//     effect->SetShaderParam("FrameID", iter->LifeUsed / iter->Life * tilesCount);

    glBegin(GL_TRIANGLE_STRIP);
    // render one particle here
    {
      glTexCoord2d(1,1); glVertex3f(+0.5f,+0.5f,0); // Top Right
      glTexCoord2d(0,1); glVertex3f(-0.5f,+0.5f,0); // Top Left
      glTexCoord2d(1,0); glVertex3f(+0.5f,-0.5f,0); // Bottom Right
      glTexCoord2d(0,0); glVertex3f(-0.5f,-0.5f,0); // Bottom Left
    }
    glEnd();
  }

  if(rt)
    rt->End();

  glDepthMask(GL_TRUE);
  glDisable(GL_BLEND);
}
